package utils

import (
	"archive/tar"
	"archive/zip"
	"compress/bzip2"
	"compress/gzip"
	"fmt"
	"github.com/ulikunitz/xz"
	"io"
	"os"
	"path/filepath"
	"time"
)

func unTar(tarReader *tar.Reader, dest string) error {
	dirTimeMap := make(map[string][]time.Time)
	for {
		header, err := tarReader.Next()
		switch {
		case err == io.EOF:
			// 所有文件提取完毕后，更改文件夹的atime和mtime
			for path, timeList := range dirTimeMap {
				err := os.Chtimes(path, timeList[0], timeList[1])
				if err != nil {
					Logger.Error(err)
					return err
				}
			}
			Logger.Debug("extract tar file done")
			return nil
		case err != nil:
			Logger.Error(fmt.Sprintf("extract tar file error: %s", err))
			return err
		case header == nil:
			continue
		}
		target := filepath.Join(dest, header.Name) //本次提取
		switch header.Typeflag {
		case tar.TypeDir:
			Logger.Debug(target)
			err := os.MkdirAll(target, os.FileMode(header.Mode))
			if err != nil {
				Logger.Error(err)
				return err
			}
			dirTimeMap[target] = []time.Time{header.AccessTime, header.ModTime}
		case tar.TypeReg:
			Logger.Debug(target)
			file, err := OpenFile(target, os.O_TRUNC, os.FileMode(header.Mode))
			if err != nil {
				return err
			}
			defer file.Close()
			_, err = io.Copy(file, tarReader)
			if err != nil {
				Logger.Error(err)
				return err
			}
			err = os.Chtimes(target, header.AccessTime, header.ModTime)
			if err != nil {
				Logger.Error(err)
				return err
			}
		default:
			Logger.Warning(fmt.Sprintf("unCatch header %s type %b with unTar", header.Name, header.Typeflag))
		}
	}
}

func unZip(zipReader *zip.ReadCloser, dest string) error {
	dirTimeMap := make(map[string][]time.Time)
	for _, file := range zipReader.File {
		target := filepath.Join(dest, file.Name)
		Logger.Debug(target)

		if file.FileInfo().IsDir() {
			err := CreateDir(target, file.Mode())
			if err != nil {
				return err
			}
			dirTimeMap[target] = []time.Time{file.Modified, file.Modified}
			continue
		}
		dstFile, err := OpenFile(target, os.O_TRUNC, file.Mode())
		if err != nil {
			return err
		}
		srcFileReader, err := file.Open()
		if err != nil {
			return err
		}
		_, err = io.Copy(dstFile, srcFileReader)
		if err != nil {
			return err
		}
		err = os.Chtimes(target, file.Modified, file.Modified)
		if err != nil {
			return err
		}

		dstFile.Close()
		srcFileReader.Close()
	}
	for path, dirTime := range dirTimeMap {
		err := os.Chtimes(path, dirTime[0], dirTime[1])
		if err != nil {
			return err
		}
	}

	return nil
}

func extractTarGz(src string, dest string) error {
	file, err := os.Open(src)
	if err != nil {
		return err
	}
	defer file.Close()

	gzipReader, err := gzip.NewReader(file)
	if err != nil {
		return err
	}
	defer gzipReader.Close()

	tarReader := tar.NewReader(gzipReader)

	return unTar(tarReader, dest)
}

func extractTar(src string, dest string) error {
	file, err := os.Open(src)
	if err != nil {
		return err
	}
	defer file.Close()

	tarReader := tar.NewReader(file)

	return unTar(tarReader, dest)
}

func extractZip(src string, dest string) error {
	zipReader, err := zip.OpenReader(src)
	if err != nil {
		return err
	}
	defer zipReader.Close()
	err = unZip(zipReader, dest)
	if err != nil {
		return err
	}
	return nil
}

func extraXz(src string, destFile string) error {
	srcFile, err := os.Open(src)
	if err != nil {
		return err
	}
	defer srcFile.Close()

	xzReader, err := xz.NewReader(srcFile)
	if err != nil {
		return err
	}

	file, err := OpenFile(destFile, os.O_TRUNC, os.FileMode(0755))
	if err != nil {
		return err
	}
	_, err = io.Copy(file, xzReader)
	if err != nil {
		return err
	}
	return nil
}

func extraTarXz(src string, dest string) error {
	file, err := os.Open(src)
	if err != nil {
		return err
	}
	defer file.Close()

	xzReader, err := xz.NewReader(file)
	if err != nil {
		return err
	}

	tarReader := tar.NewReader(xzReader)

	return unTar(tarReader, dest)
}

func extraTarBz2(src string, dest string) error {
	file, err := os.Open(src)
	if err != nil {
		return err
	}
	defer file.Close()

	bz2Reader := bzip2.NewReader(file)

	tarReader := tar.NewReader(bz2Reader)

	return unTar(tarReader, dest)
}

// ExtractFile 根据文件后缀选择解压方法
func ExtractFile(src string, dest string, suffix string) error {

	Logger.Debug(fmt.Sprintf("extract file,suffix: %s", suffix))

	switch suffix {
	case "tar.gz", "tgz":
		err := extractTarGz(src, dest)
		if err != nil {
			return err
		}
	case "tar.xz", "txz":
		err := extraTarXz(src, dest)
		if err != nil {
			return err
		}
	case "tar.bz2":
		err := extraTarBz2(src, dest)
		if err != nil {
			return err
		}
	case "zip":
		err := extractZip(src, dest)
		if err != nil {
			return err
		}
	case "xz":
		err := extraXz(src, dest)
		if err != nil {
			return err
		}
	case "tar":
		err := extractTar(src, dest)
		if err != nil {
			return err
		}
	default:
		e := fmt.Sprintf("Unsupported file extract format %s", suffix)
		Logger.Error(e)
		return fmt.Errorf(e)
	}
	return nil
}
